<!DOCTYPE html>
<head>
  <title>Porffor</title>

  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">

  <meta itemprop="name" content="Porffor">
  <meta property="og:title" content="Porffor">
  <meta itemprop="description" content="Porffor compiles JavaScript ahead-of-time to WebAssembly and native binaries.">
  <meta property="og:description" content="Porffor compiles JavaScript ahead-of-time to WebAssembly and native binaries.">
  <meta property="og:type" content="website">

  <link rel="icon" type="image/png" href="logo.png">

  <style>
    @import url(https://fonts.bunny.net/css?family=jetbrains-mono:400,600,800);

    :root {
      font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;

      --header-primary: #ffffff;
      --header-secondary: #b9bbbe;

      --text-normal: #ffffff;
      --text-muted: #d0d4d8;
      color: var(--text-muted);

      --accent-dark: #3e2066;
      --accent: #8545cf;
      --accent-light: #9c60e0;

      --background-primary: #100420;
      --background-secondary: #200840;
    }

    html, body {
      margin: 0;
      padding: 0;
    }

    body {
      background: var(--background-primary);
    }

    * {
      box-sizing: border-box;
    }

    h1 {
      font-weight: 800;
      font-size: 32px;

      color: var(--header-primary);

      margin: 0;
    }

    h2, details > summary {
      font-weight: 600;
      font-size: 20px;

      color: var(--header-primary);

      margin-top: 60px;
      margin-bottom: 8px;
    }

    h2 {
      font-weight: 800;
      font-size: 28px;
      color: var(--header-primary);
      margin: 0;
      margin-top: 100px;
      margin-bottom: 40px;
    }

    details > summary {
      margin-top: 20px;
      margin-bottom: 0;
      cursor: pointer;
    }

    .gui-header > :first-child {
      font-size: 90%;
      display: inline;
      color: #57f287;
      margin-left: 0.2rem;
      font-weight: 600;
    }

    .gui-header {
      margin-bottom: 12px;
      margin-top: 60px;
    }

    .gui-header + p {
      margin: 0;
      margin-bottom: 24px;
    }

    header {
      width: 100%;
      height: fit-content;
      padding: 8px 24px;

      background: rgba(10, 2, 10, 0.6);
      backdrop-filter: blur(4px) saturate(0.6);
      border-bottom: 2px solid var(--accent);

      position: sticky;
      top: 0;
      z-index: 10;
    }

    header > div {
      margin: 0 auto;
      width: 1600px;
      max-width: 100%;

      display: flex;
      align-items: center;
    }

    header h1 {
      color: var(--accent);
      font-size: 38px;
    }

    header > div > div {
      flex-grow: 1;
      margin-left: 80px;

      display: flex;
      /* gap: 60px; */

      color: #505458;
      font-size: 26px;
    }

    header .shown-600 {
      filter: grayscale(1);
    }

    header .shown-600:hover {
      filter: brightness(2);
    }

    header a {
      color: #e0e4e8;
      text-decoration: none !important;
      font-weight: 600;
    }

    header a:hover {
      color: var(--header-primary);
      font-weight: 800;
    }

    a:not([href]) {
      cursor: pointer;
      color: var(--accent);
    }

    a:not([href]):hover {
      color: var(--accent-light);
    }

    a[href]:hover {
      text-decoration: underline var(--accent);
    }

    article {
      padding: 12px 24px;
      width: 100%;
      max-width: 1648px;
      margin: auto;
      padding-bottom: 80px;
    }

    p {
      font-size: 20px;
      font-weight: 400;
      color: var(--text-muted);

      margin-bottom: 20px;
      margin-top: 20px;
    }

    hr {
      margin-top: 50px;
      margin-bottom: 50px;
      border: 1px solid var(--accent);
    }

    hr.invis {
      border: 0;
    }

    .dual {
      width: 100%;
      margin-bottom: 40px;

      display: flex;
    }

    .cards {
      display: grid;
      grid-template-columns: repeat(2, 1fr);
      /* gap: 60px; */
    }

    .cards.three {
      grid-template-columns: repeat(3, 1fr);
      margin-bottom: 60px;
    }

    .cards.four {
      grid-template-columns: repeat(4, 1fr);
    }

    .cards.three > div,
    .cards.four > div {
      border-top: none;
    }

    .columns {
      grid-template-columns: repeat(2, 1fr);
    }

    .cards > div {
      display: flex;
      flex-direction: column;

      padding: 24px;

      border: 2px solid var(--accent);
      /* border: 2px solid #505458; */
    }

    .cards > :not(:last-child) {
      border-right: none;
    }

    .cards > div > h1 {
      font-weight: 800;
      color: var(--header-primary);
      margin-top: 0;
    }

    .cards > div > h1 + p {
      margin-top: 18px;
    }

    .cards > div > p {
      color: var(--text-muted);
      font-weight: 400;

      margin-top: 28px;
      margin-bottom: 0;
    }

    .cards > div > p:first-child {
      margin-top: 0;
    }

    .cards > div > ul {
      color: var(--text-muted);
      font-weight: 400;
      font-size: 20px;

      margin-top: 10px;
      margin-bottom: 0px;
    }

    .cards li {
      margin-bottom: 16px;
    }

    .cards > span {
      color: var(--text-muted);
      font-weight: 600;
      font-size: 28px;

      display: flex;
      align-items: center;
      justify-content: center;
    }

    .bars {
      margin-top: 60px;
      margin-bottom: 60px;

      display: flex;
      flex-direction: column;
      gap: 40px;

      width: 100%;
      align-items: center;
    }

    .bars > p {
      font-size: 1em;
    }

    .bars > div {
      display: flex;
      flex-direction: column;
      align-items: center;
      width: 80%;
      gap: 0.6rem;
      position: relative;
    }

    .bars > div > :first-child {
      position: absolute;
      left: -10ch;
      color: var(--header-primary);
      font-weight: 400;
      font-size: 18px;
      text-align: right;

      width: 6ch;
      margin-right: 20px;
    }

    .bars > div > div > span {
      color: var(--text-muted);
      font-weight: 300;
      font-size: 18px;

      width: 3ch;
      margin-right: 20px;
    }

    .bars > div > div {
      width: 100%;
      display: flex;
    }

    .bars > div > div > div {
      background: var(--background-secondary);
      width: calc((var(--time) / 350) * 100%);
      height: 1.4rem;
    }

    @keyframes fill {
      0% {
        width: 0%;
      }
      100% {
        width: 100%;
      }
    }

    .bars > div > div > div > div {
      background: var(--color);
      height: 100%;
      animation: calc(var(--time) * 0.01s) linear fill;
      /* animation: calc(var(--time) * 0.01s) linear fill infinite; */
      /* animation-delay: calc((400 - var(--time)) * 0.01s); */
    }

    .bars > div > div > :last-child {
      color: var(--text-muted);
      font-weight: 400;
      margin-left: 8px;
      font-size: 1em;
    }

    a {
      color: var(--accent-light);
      text-decoration: none;
      font-weight: 600;

      display: inline-block;
    }

    ::-webkit-scrollbar {
      width: 12px;
    }

    ::-webkit-scrollbar-corner {
      background-color: transparent;
    }

    ::-webkit-scrollbar-thumb {
      background-color: #303438;
    }

    ::-webkit-scrollbar-track {
      background-color: #000408;
    }

    ::-webkit-scrollbar-thumb,
    ::-webkit-scrollbar-track {
      border: 4px solid transparent;
      background-clip: padding-box;
      border-radius: 12px;
    }

    ul,
    ol {
      margin-top: 0px;
      margin-bottom: 0px;
    }

    code {
      color: #a0a4a8;
    }

    .tagline {
      font-size: 48px;
      margin-top: 16px;
    }

    .tagline + p {
      font-size: 24px;
      margin-top: 12px;
      margin-bottom: 60px;
    }

    #split {
      border-top: 2px solid var(--accent);
      display: grid;
      grid-template-columns: 60% 1fr;

      width: 100%;
      height: 60vh;
    }

    #split > :last-child {
      background: #0c0c0c;
      color: #d0d0d0;

      padding: 6px;

      font-size: 14px;
      border-left: 2px solid var(--accent);

      white-space: pre;

      height: 100%;
      overflow-x: hidden;
      overflow-y: auto;
    }

    #output,
    #status {
      border-top: 2px solid var(--accent);
      color: var(--text-muted);
      background: var(--background-secondary);

      width: 100%;
      height: auto;
      font-size: 14px;
      padding: 6px;

      white-space: pre;

      overflow-x: hidden;
      overflow-y: auto;
    }

    #status {
      padding: 4px;
      font-size: 14px;
      height: 26px;
    }

    .ansi-31 {
      color: rgb(197, 15, 31);
    }
    .ansi-34 {
      color: rgb(0, 55, 218);
    }
    .ansi-36 {
      color: rgb(58, 150, 221);
    }
    .ansi-35 {
      color: rgb(136, 23, 152);
    }
    .ansi-95 {
      color: rgb(180, 0, 158);
    }
    .ansi-33 {
      color: rgb(193, 156, 0);
    }
    .ansi-2, .ansi-90 {
      color: rgb(118, 118, 118);
    }

    #wasm-size {
      position: absolute;
      right: 6px;
      top: 6px;
      pointer-events: none;
    }

    #split > * {
      position: relative;
    }

    #js-size {
      position: absolute;
      right: 24px;
      top: 6px;
      pointer-events: none;

      z-index: 9;

      font-size: 14px;
      color: #d0d0d0;

      display: none;
    }

    #args {
      background: var(--background-secondary);

      width: calc(100% - 800px);
      margin: 0;

      color: var(--text-normal);
      border: 0;
      padding: 6px;
      float: right;
      font-size: 14px;
    }

    #examples_dropdown {
      width: 180px;
      padding: 4px;
      height: 29px;
      display: inline-block;

      color: var(--text-normal);
      font-size: 16px;
      background: var(--background-secondary);
      border: none;

      margin-right: 20px;
    }

    #valtype_dropdown,
    #opt_dropdown,
    #parser_dropdown,
    #target_dropdown {
      width: fit-content;
      padding: 4px;
      height: 29px;
      display: inline-block;

      color: var(--text-normal);
      font-size: 14px;
      background: var(--background-secondary);
      border: none;

      margin-left: 4px;
      margin-right: 12px;
    }

    #graph {
      width: 100%;
      height: 800px;
      position: relative;
      margin-bottom: 60px;
    }

    #graph span {
      position: absolute;
      right: 12px;
      top: 50px;
      font-size: 12px;
      color: var(--text-muted);
      font-weight: 400;
      cursor: default;
    }

    #graph_legend {
      position: absolute;
      right: 28px;
      top: 34px;

      display: flex;

      width: 700px;
      justify-content: space-between;

      color: var(--text-muted);
      font-size: 20px;
    }

    #graph_legend div::before {
      content: "■";
      font-size: 200%;
      vertical-align: top;
      line-height: 0.5;
      display: inline-block;
      color: var(--color);
      margin-right: 12px;
    }

    a.simple {
      color: inherit;
      font-weight: inherit;
    }

    a.simple:hover {
      text-decoration: underline;
    }

    a.simple::after {
      display: none;
    }

    #graph svg {
      width: 100%;
      height: 100%;
    }

    #graph text {
      cursor: default;
      fill: #505458;
      font-size: 22px;
      text-rendering: optimizeLegibility;
    }

    span[title] {
      text-decoration: underline 1px dotted var(--text-muted);
    }

    pre {
      color: var(--text-muted);
      line-height: 1;
      font-size: max(12px, min(18px, 1.2vw));
      margin: 0;
      position: relative;
    }

    .browser-js-marker::before {
      content: "Fast startup ▲\A              ┆\A              ┆\A              ┆\A              ┆\A              ┆\A              ┆\A              ┆\A              ┆\A              ┆\A              ┆\A              ┆\A              ┆\A              ┆\A              ┆\A      Fast JS ▼";
      color: var(--text-muted);
      position: absolute;
      left: -180px;
      font-weight: 400;
      margin-top: 24px;
    }

    pre h2 {
      font-weight: 800;
      font-size: 100%;
      margin: 0;
      display: inline;
    }

    b {
      color: var(--text-normal);
      font-weight: 600;
    }

    .accent {
      color: var(--accent-light);
    }

    @media (max-width: 1000px) {
      .cards {
        grid-template-columns: 1fr !important;
      }

      .cards > :not(:last-child) {
        border-right: 2px solid var(--accent);
      }

      .hidden-1000 {
        display: none;
      }

      #graph ~ * {
        display: none !important;
      }

      #graph_legend {
        width: calc(100% - 12px * 2);
        right: 12px;
        flex-wrap: wrap;
      }

      h2 span {
        font-size: min(3.5vw, 80%);
      }

      header h1 {
        font-size: 32px;
      }

      header > div > div {
        font-size: 22px;
      }
    }

    @media (max-width: 800px) {
      .cards > div {
        border: none !important;
        border-bottom: 2px solid var(--accent) !important;
        padding: 0;
        padding-bottom: 40px;
        margin-bottom: 40px;
      }

      pre, #graph, .bars {
        display: none;
      }

      .dual {
        margin: 0;
      }

      .dual + p {
        margin-top: 0;
      }

      .cards.three {
        margin-bottom: 0;
      }
    }

    .shown-600 {
      display: none;
    }

    @media (max-width: 600px) {
      .tagline {
        font-size: max(24px, 7vw);
      }

      .tagline + p {
        font-size: max(18px, 3.6vw);
      }

      p {
        font-size: 18px;
      }

      header h1 {
        font-size: 24px;
      }

      header > div > div {
        font-size: 20px;
        flex-wrap: wrap;
        align-content: center;
        justify-content: space-around;
        margin-left: 20px;
      }

      header > div > div > div {
        display: none;
      }

      .hidden-600 {
        display: none;
      }

      .shown-600 {
        display: inline;
      }
    }

    .new {
      color: var(--text-normal);
      background: var(--accent);
      padding: 0.4em 0.8em;
      font-weight: 600;
      /* font-size: 1.2em; */
      margin-right: 0.4em;
    }
  </style>
</head>

<body>
  <header>
    <div>
      <h1>Porffor</h1>

      <div>
        <a href="https://github.com/CanadaHonk/porffor">Code</a><span class="hidden-600">&nbsp;|&nbsp;</span>
        <a href="https://discord.gg/wPV3WgDGwg">Chat</a><div style="flex-grow: 1"></div>
        <a href="https://x.com/CanadaHonk"><span class="hidden-600">Twitter</span><span class="shown-600">🐦</span></a><span class="hidden-600">&nbsp;|&nbsp;</span>
        <a href="https://bsky.app/profile/goose.icu"><span class="hidden-600">Bluesky</span><span class="shown-600">🦋</span></a>
      </div>
    </div>
  </header>

  <article>
    <h1 class="tagline">An ahead-of-time JavaScript compiler</h1>
    <p>
      Porffor compiles JavaScript ahead-of-time to WebAssembly and native binaries.<br>
      It is currently in <b>pre-alpha</b> with usability beginning in 2025.
    </p>


    <section class="bars">
      <div>
        <span style="color: var(--accent-light)">Porffor</span>
        <div>
          <span>P50</span>
          <div style="--time: 16.3; --color: var(--accent)"><div></div></div>
          <span>16.3ms</span>
        </div>
        <div>
          <span>P90</span>
          <div style="--time: 27.5; --color: var(--accent)"><div></div></div>
          <span>27.5ms</span>
        </div>
        <div>
          <span>P99</span>
          <div style="--time: 41.1; --color: var(--accent)"><div></div></div>
          <span>41.1ms</span>
        </div>
      </div>

      <div>
        <span>Node</span>
        <div>
          <span>P50</span>
          <div style="--time: 194; --color: #68bc4a"><div></div></div>
          <span>194ms</span>
        </div>
        <div>
          <span>P90</span>
          <div style="--time: 256; --color: #68bc4a"><div></div></div>
          <span>256ms</span>
        </div>
        <div>
          <span>P99</span>
          <div style="--time: 323; --color: #68bc4a"><div></div></div>
          <span>323ms</span>
        </div>
      </div>

      <p>
        <span class="new">new!</span> Cold start times for an AWS Lambda hello world function (<a href="https://goose.icu/lambda/">source and methodology</a>)
      </p>
    </section>

    <section class="cards">
      <div>
        <h1>JS -> Wasm</h1>
        <p>
          Porffor's WebAssembly output is
          <b>10-30x smaller and faster</b> compared to existing JS -> Wasm
          projects as Porffor compiles JS instead of bundling an interpreter.
        </p>
        <p>
          JS as Wasm allows for sandboxed execution but suffers drastic
          performance losses: <b>Porffor solves this</b>, allowing for:
        </p>

        <details>
          <summary>Secure, efficient server-side JS hosting</summary>
          <p>
            For edge runtimes, Wasm sandboxing allows secure execution without
            needing excessive isolation; combined with AOTs low overhead
            compared to JITs, Porffor could allow for running many more
            customers on the same hardware with minimal performance loss.
          </p>
        </details>

        <details>
          <summary>Reverse-engineering resistance</summary>
          <p>
            If you have especially sensitive JS, compilation can be much harder
            to reverse than code obfuscation.
          </p>
        </details>
      </div>

      <div>
        <h1>JS -> Native</h1>
        <p>
          As Porffor truly compiles JS without packaging a runtime,
          binary sizes are up to <b>1000x smaller</b> (~90MB -> &lt;100KB).
        </p>
        <p>
          JS as native allows for simple one-click execution but has substandard
          sizes: <b>Porffor solves this</b>, allowing for:
        </p>

        <details>
          <summary>Fast JS for embedded, game consoles, and more</summary>
          <p>
            Under the hood, Porffor compiles JS -> C -> native; anywhere you can
            use C, you can now use JS!
          </p>
        </details>

        <details>
          <summary>Tiny CLI apps written in JS</summary>
          <p>
            Imagine writing a CLI app in JS and compiling it to a one-click
            executable under 1MB!
          </p>
        </details>
      </div>
    </section>

    <section class="cards three">
      <div>
        <p>
          <b>Porffor has safe compilation</b> as it is written in JS (preventing memory safety vulnerabilities) with 0 eval.
        </p>
      </div>
      <div>
        <p>
          <b>Porffor is written from scratch</b> with ahead-of-time in mind allowing for previously impossible optimizations.
        </p>
      </div>
      <div>
        <p>
          <b>Porffor natively supports TypeScript</b>, no build step: just give it a TS file.
        </p>
      </div>
    </section>

    <section class="dual">
      <pre>
<h2>Compile-time (developer's machine)</h2>


<br>
<br>
<br>


<br>
<br>
<br>


<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>

<b>Traditional embedded compiler</b>
┌──────────────┐      ┌────────────┐
│              │      │            │
│  JavaScript  ├─────►│  Bytecode  ├<span class="hidden-1000">─────────────────────</span>────────────►
│              │      │            │
└──────────────┘      └────────────┘


<b class="accent">Porffor (AOT compiler)</b>
┌──────────────┐      ┌──────────────────────┐
│              │      │                      │
│  JavaScript  ├─────►│ Binary (Wasm/native) ├<span class="hidden-1000">─────────────────────</span>──►
│              │      │                      │
└──────────────┘      └──────────────────────┘
</pre><pre>
<h2>Runtime (user's machine)</h2>

<b>Interpreter</b>
┌──────────────┐      ┌────────────┐      ┌─────────────┐
│              │      │            │      │             │
│  JavaScript  ├─────►│  Bytecode  ├─────►│  Evaluated  │
│              │      │            │      │             │
└──────────────┘      └────────────┘      └─────────────┘


<b>Just-in-time compiler (JIT)</b>
┌──────────────┐      ┌──────────────┐      ┌────────────┐
│              │      │              │      │            │
│  JavaScript  ├─────►│ Machine code ├─────►│  Executed  │
│              │      │              │      │            │
└──────────────┘      └──────────────┘      └────────────┘


<b class="browser-js-marker">Browser JS engine</b>
┌──────────────┐               ┌─────────────┐
│              │  Interpreter  │             │
│  JavaScript  ├───────┬──────►│  Evaluated  │
│              │       │       │             │
└──────────────┘       |       └─────────────┘
                       │
                       │              ┌─────────────┐
                       │  JIT Tier 1  │             │
                       └───────┬─────►│  Evaluated  │
                               │      │             │
                               |      └─────────────┘
                               │
                               │              ┌─────────────┐
                               │  JIT Tier 2  │             │
                               └───────┬─────►│  Evaluated  │
                                       │      │             │
                                      ...     └─────────────┘


<b>Traditional embedded interpreter</b>
┌─────────────┐
│             │
│  Evaluated  │
│             │
└─────────────┘


<b>Wasm runtime or native machine</b>
┌────────────┐
│            │
│  Executed  │
│            │
└────────────┘
</pre>
    </section>

    <p>Instead of a slow interpreter or multiple JIT tiers carefully balancing compile speed (startup/loading time) and JS performance, compiling AOT allows you to compile first and run later. While compile speed for AOT remains important for DX, it no longer effects UX; allowing Porffor to perform static analysis for optimizations like C++ and Rust do today.</p>

    <p id="test262">The main drawbacks of AOT is lack of dynamic JS evaluation (like <code>eval</code>) and needing to reinvent the JS engine, so as Porffor is early a lot of JS doesn't work yet, but it is improving by the day:</p>

    <!-- <hr> -->
    <h2 class="gui-header">Test262 <pre id="test262_percent"></pre></h2>
    <p>
      Porffor is run against <a class="simple" href="https://github.com/tc39/test262">Test262</a>, the official ECMAScript conformance test suite, every commit to track conformance progress.
    </p>
    <div id="graph">
      <div id="graph_legend"></div>
      <svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none" id="svg"></svg>
    </div>

    <!-- <hr> -->
    <h2 class="gui-header">Playground</h2>
    <p>You can try Porffor here or locally with <code>npm i -g porffor@latest && porf</code></p>
    <!-- <select id="examples_dropdown"></select> -->
    <!-- Valtype <select id="valtype_dropdown"></select> -->
    <!-- <span title="Optimization level">-O</span> <select id="opt_dropdown"></select> -->
    <!-- <span title="JS parser to use">Parser</span> -->
    <!-- <select id="parser_dropdown"></select> -->
    <!-- <span title="Experimental!">Target</span> -->
    <!-- <select id="target_dropdown"></select> -->
    <!-- <input type="text" placeholder="Extra arguments" id="args" /> -->
    <div id="split"></div>
    <div id="output"></div>
    <div id="status"></div>
  </article>

  <script>
    (async () => {
      const loadScript = async (x) => {
        const el = document.createElement('script');
        el.src = x;
        document.head.append(el);

        await new Promise((res) => (el.onload = res));
      };

      if (!window.monaco) {
        await loadScript('https://cdn.openasar.dev/monaco-editor/min/vs/loader.js');

        require.config({
          paths: { vs: 'https://cdn.openasar.dev/monaco-editor/min/vs' }
        });
        await new Promise((res) => require(['vs/editor/editor.main'], res));
      }

      const monacoContainer = document.createElement('div');
      split.appendChild(monacoContainer);

      const jsSize = document.createElement('div');
      jsSize.id = 'js-size';
      monacoContainer.append(jsSize);

      const wasmView = document.createElement('div');
      split.appendChild(wasmView);

      const status = document.getElementById('status');
      let code = `
console.log(\`Hello from \${navigator.userAgent}! Did you know:\\n\`);

const rows = [
  [ 'Engine', 'Execution model' ],
  [ 'Porffor', 'Compiles ahead-of-time (Wasm + native)' ],
  [ 'V8', 'Compiles just-in-time' ],
  [ 'SpiderMonkey', 'Compiles just-in-time' ],
  [ 'JavaScriptCore', 'Compiles just-in-time' ],
  [ 'QuickJS', 'Interprets' ],
  [ 'Hermes', 'Interprets (ahead-of-time bytecode)' ]
];

const widths = [0, 0];
for (const r of rows) {
  if (r[0].length > widths[0]) widths[0] = r[0].length;
  if (r[1].length > widths[1]) widths[1] = r[1].length;
}

const border = '+' + '-'.repeat(widths[0] + 2) + '+' + '-'.repeat(widths[1] + 2) + '+';
console.log(border);
for (let i = 0; i < rows.length; i++) {
  const r = rows[i];
  console.log('| ' + r[0].padEnd(widths[0], ' ') + ' | ' + r[1].padEnd(widths[1], ' ') + ' |');
  if (i === 0) console.log(border);
}
console.log(border);
`.trim();

      const addOptions = (container, options, def) => {
        for (const x of options) {
          const el = document.createElement('option');
          el.textContent = x;
          el.selected = x === def;

          container.appendChild(el);
        }
      };

      // if (location.hash) {
      //   code = atob(location.hash.slice(1));
      // }

      // addOptions(valtype_dropdown, [ 'i32', 'f64' ], 'f64');
      // addOptions(opt_dropdown, [ 0, 1, 2, 3 ], 1);
      // addOptions(
      //   parser_dropdown,
      //   ['acorn', 'meriyah', 'hermes-parser', '@babel/parser'],
      //   'acorn',
      // );
      // addOptions(target_dropdown, ['wasm', 'c'], 'wasm');

      // parser_dropdown.oninput = async () => {
      //   setProcess();
      //   globalThis.argvChanged?.();
      //   await globalThis._porf_loadParser();
      //   comp();
      // };

      // target_dropdown.oninput = () => comp();

      window.editor = monaco.editor.create(monacoContainer, {
        value: code,
        codeLens: false,
        language: 'typescript',
        theme: 'vs-dark',
        minimap: {
          enabled: false,
        },
      });

      const debounce = (handler, timeout) => {
        let timer;
        return (...args) => {
          clearTimeout(timer);
          timer = setTimeout(() => handler(...args), timeout);
        };
      };

      const setProcess = () => {
        globalThis.process = {
          // argv: ['', '', ...args.value.split(' '), `-O${opt_dropdown.value}`, `--parser=${parser_dropdown.value}`, `--target=${target_dropdown.value}`]
          argv: [
            '',
            '',
            // ...args.value.split(' '),
            // `--parser=${parser_dropdown.value}`,
            // `--target=${target_dropdown.value}`,
            '--disassemble',
          ],
        };
      };

      setProcess();
      // load version
      globalThis.version = (await (await fetch('../runtime/index.js')).text()).split('\'')[3];
      const compile = (await import('../compiler/wrap.js')).default;

      const comp = async () => {
        setProcess();
        globalThis.argvChanged?.();

        // location.hash = '#' + btoa(code);

        jsSize.textContent = `${new Blob([code]).size} bytes`;

        const ansi = x => x.replaceAll('\x1B[0m', '</span>').replace(/\x1B\[([0-9]{1,2})m/g, (_, esc) => `<span class='ansi-${esc}'>`);

        let cache = '';
        const print = (str) => {
          cache += str;
        };

        output.textContent = '';
        wasmView.innerHTML = '';
        status.textContent = 'Compiling...';

        let wasm, exports, times, disasms, c;
        try {
          ({ wasm, exports, times, disasms, c } = compile(code, true, print));
        } catch (e) {
          console.error(e);
          status.textContent = `${e.constructor.name}: ${e.message}`;
          return;
        }

        // if (target_dropdown.value === 'c') {
        //   wasmView.textContent = c.trim();
        // } else {
          wasmView.innerHTML =
            `<div id='wasm-size'>${wasm.byteLength} bytes</div>` +
            ansi(disasms.join('\n'));
        // }

        status.textContent = `Compiled in ${times[0].toFixed(0)}ms`;

        await new Promise((res) => setTimeout(res, 10));

        const t2 = performance.now();
        try {
          exports.main();
        } catch (e) {
          console.error(e);
          status.textContent = `${e.constructor.name}: ${e.message}`;
          return;
        }

        print('\n');

        const execTime = performance.now() - t2;
        status.textContent += `. Executed in ${execTime.toFixed(0)}ms`;

        output.innerHTML = ansi(cache);
      };

      const compDebounce = debounce(comp, 500);

      editor.getModel().onDidChangeContent((e) => {
        code = editor.getValue();
        compDebounce();
      });

      // args.oninput = () => compDebounce();

      comp();

      const niceDate = (x) => {
        return x.toLocaleString('default', {
          month: 'long',
          day: 'numeric'
        });
      };

      const history = await (await fetch('/test262/history.json')).json();
      const percent = history[0].results[0];
      test262_percent.innerHTML = `${percent.toFixed(2)}% <span style="color: var(--text-muted); font-size: 80%">${'█'.repeat(Math.floor(percent / 4))}${'░'.repeat(Math.ceil((100 - percent) / 4))}</span>`;

      const colors = {
        pass: '#57f287',
        fail: '#fee75c',
        runtimeError: '#ed4245',
        compileError: '#6f0b0c',
      };

      for (const x in colors) {
        const name = x.replace(
          /[a-z][A-Z]/,
          (_) => `${_[0]} ${_[1].toLowerCase()}`,
        );
        const el = document.createElement('div');
        el.textContent = name[0].toUpperCase() + name.slice(1);
        el.style = `--color: ${colors[x]}`;

        graph_legend.appendChild(el);
      }

      const makeGraph = () => {
        const oldest = new Date(history.at(-1).time);
        const now = new Date();
        // const months =
        //   now.getMonth() -
        //   oldest.getMonth() +
        //   12 * (now.getFullYear() - oldest.getFullYear());
        // graph_time.textContent = `All-time (${months} months)`;

        const newest = history[0].time;
        const dateRange = newest - oldest;

        const availableWidth = Math.min(window.innerWidth, 2000) - 400 - 24;
        const availableHeight = 800;
        const aspectRatio = availableWidth / availableHeight;

        const width = 1000 * aspectRatio;
        const height = 1000;

        const leftAxisWidth = 70;
        const paddingX = 0;
        const paddingY = 10;

        svg.setAttribute(
          'viewBox',
          `0 0 ${width + leftAxisWidth + paddingX * 2} ${height + paddingY * 2}`,
        );
        svg.innerHTML = '';

        // left axis line
        // const leftAxisLine = document.createElementNS(
        //   'http://www.w3.org/2000/svg',
        //   'line',
        // );
        // leftAxisLine.setAttribute('x1', padding + leftAxisWidth);
        // leftAxisLine.setAttribute('x2', padding + leftAxisWidth);
        // leftAxisLine.setAttribute('y1', padding);
        // leftAxisLine.setAttribute('y2', height + padding);
        // leftAxisLine.setAttribute('stroke', '#909498');
        // svg.appendChild(leftAxisLine);

        const gridlinesEvery = 10;
        // for (let i = lowerBound; i <= upperBound; i += gridlinesEvery) {
        for (let i = 0; i <= 100; i += gridlinesEvery) {
          const line = document.createElementNS(
            'http://www.w3.org/2000/svg',
            'line',
          );
          line.setAttribute('x1', (leftAxisWidth + paddingX).toFixed());
          line.setAttribute('x2', (width + leftAxisWidth + paddingX).toFixed());

          // const y = (padding + (1 - ((i - lowerBound) / (upperBound - lowerBound))) * height).toFixed();
          const y = (paddingY + (1 - i / 100) * height).toFixed();
          line.setAttribute('y1', y);
          line.setAttribute('y2', y);
          line.setAttribute('stroke', '#505458');
          svg.appendChild(line);

          const text = document.createElementNS(
            'http://www.w3.org/2000/svg',
            'text',
          );
          text.setAttribute('x', paddingX + leftAxisWidth - 12);
          // text.setAttribute('y', +y + ((i - lowerBound) / (upperBound - lowerBound)) * 10);
          text.setAttribute('y', +y + 7);
          text.setAttribute('text-anchor', 'end');

          text.textContent = `${i}%`;
          svg.appendChild(text);
        }

        const points = {
          pass: [],
          fail: [],
          runtimeError: [],
          compileError: [],
        };

        for (let i = 0; i < history.length; i++) {
          const [
            percent,
            total,
            pass,
            fail,
            runtimeError,
            wasmError,
            compileError,
            timeout
          ] = history[i].results;

          const when = (history[i].time - oldest) / dateRange;
          const x = (paddingX + leftAxisWidth + when * width).toFixed();

          const run = (n, type) => {
            const y = (paddingY + (1 - n / total) * height).toFixed();
            points[type].push(`${x},${y}`);
          };

          run(pass, 'pass');
          run(fail, 'fail');
          run(runtimeError + timeout, 'runtimeError');
          run(compileError + wasmError, 'compileError');
        }

        const addLine = (points, color) => {
          const polyline = document.createElementNS(
            'http://www.w3.org/2000/svg',
            'polyline',
          );
          polyline.setAttribute('fill', 'none');
          polyline.setAttribute('stroke', color);
          polyline.setAttribute('stroke-width', 3);
          polyline.setAttribute('points', points.join(' '));
          svg.appendChild(polyline);
        };

        for (const x in points) {
          addLine(points[x], colors[x]);
        }
      };
      makeGraph();
      window.addEventListener('resize', makeGraph);
    })();
  </script>
</body>